using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Diagnostics;
using System.Collections;

namespace The_LarX
{
    public class Animation_I3DObject : ICloneable
    {
        /// <summary>
        /// All the Frames this Animation has
        /// </summary>
        public List<KeyFrame_I3DObject> Frames = new List<KeyFrame_I3DObject>();

        /// <summary>
        /// The current reproducing Frame.
        /// </summary>
        public int CurrentFrame
        {
            get { return currentFrame; }
            set
            {
                bool adding = value > currentFrame;
                int difference = value - currentFrame;
                currentFrame = value;
                UpdateAnimation(adding, difference);
            }
        }
        private int currentFrame = 0;

        /// <summary>
        /// The Speed this Animation should play. 
        /// </summary>
        public int Speed { get; set; }

        /// <summary>
        /// If it should ApplyPosition. This is util, so we can
        /// only apply Rotation/Scale, making files reusable. EXAMPLE:
        /// I make a sample of a door turning. Then I save it's file.
        /// Now I can load this file in several 3D Models, and they will
        /// rotate fine, whetever they are on the 3D World.
        /// </summary>
        public bool ApplyPosition = true;
                
        /// <summary>
        /// Returns the total Number of Frames. Beware, this is the total of frames,
        /// not the count of frames. 
        /// </summary>
        public int NumberOfFrames
        {
            get
            {
                int Max = -1;
                for (int i = 0; i < Frames.Count; i++)
                {
                    if (Frames[i].Frame > Max)
                    {
                        Max = Frames[i].Frame;
                    }
                }
                if (Max == -1)
                    return 0;
                else
                    return Max;
            }            
        }

        /// <summary>
        /// Playing
        /// </summary>
        public bool Play
        {
            get { return play; }
            set { play = value; }
        }
        /// <summary>
        /// Playing Backward
        /// </summary>
        public bool PlayBackward
        {
            get { return playBack; }
            set { playBack = value; }
        }
        private bool play;
        private bool playBack;

        /// <summary>
        /// If we should'nt Restart the animation when it gets to the ened.
        /// </summary>
        public bool Stop
        {
            get { return stop; }
            set { stop = value; }
        }
        private bool stop;
                
        protected IAnimateableI3DObject obj;
        /// <summary>
        /// The Object we're animating
        /// </summary>
        public IAnimateableI3DObject Object
        {
            get { return obj; }
            set 
            { 
                obj = value;                
            }
        }

        /// <summary>
        /// Used by the Editor, the object parent of this Animation class
        /// </summary>
        public object Parent;        

        /// <summary>
        /// If it should Interpolate between frames. It's like that:
        /// I have 2 frames, one in 0 frame, with position (0,0,0),
        /// and the another in 50, with position (5,5,5). 
        /// If it doesnt interpolate, In frame 0, it will be (0,0,0), in 49,
        /// still (0,0,0), so, in 50, (5,5,5). If interpolate, in 1 it will be
        /// (0.something, 0.something, 0.something), trying to reach (5,5,5).
        /// In frame 49 it will almost be 50, it's interpolating.
        /// </summary>
        public bool InterpolatedAnimation
        {
            get { return interpolated; }
            set { interpolated = value; }
        }
        protected bool interpolated = true;
            
        /// <summary>
        /// Initializes the Animation class
        /// </summary>
        public Animation_I3DObject()
        {
            Speed = 1;
        }
        public Animation_I3DObject(IAnimateableI3DObject obj)
        {
            this.Object = obj;
            Speed = 1;
        }

        /// <summary>
        /// Save the current Animation ---
        /// Only works on WINDOWS
        /// </summary>
        /// <param name="FileName">The place to save it</param>
        public void Save(string FileName)
        {
            Serializer.SaveCustom(Frames, FileName);
        }
        /// <summary>
        /// Loads an Animation
        /// </summary>
        /// <param name="FileName">The place to load it</param>
        public void Load(string FileName)
        {
            Frames = Serializer.LoadCustom<List<KeyFrame_I3DObject>>(FileName);            
        }
        /// <summary>
        /// Gets a KeyFrame by its frame
        /// </summary>
        /// <param name="Frame">The KeyFrame's frame</param>
        /// <returns>The KeyFrame</returns>
        public KeyFrame_I3DObject FrameByNumber(int Frame)
        {
            for (int i = 0; i < Frames.Count; i++)
            {
                if (Frames[i].Frame == Frame)
                {
                    return Frames[i];
                }
            }
            return null;
        }

        /// <summary>
        /// Create a KeyFrame
        /// </summary>
        /// <param name="frame">The Frame it is</param>
        public virtual void SetKey(int frame, Vector3 Pos, Vector3 Rot, Vector3 Sca)
        {
            KeyFrame_I3DObject Frame = new KeyFrame_I3DObject();
            Frame.Position = Pos;
            Frame.Rotation = Rot;
            Frame.Scale = Sca;
            Frame.Frame = frame;
            for (int i = 0; i < Frames.Count; i++)
            {
                if (Frames[i].Frame == frame)
                {
                    Frames.Remove(Frames[i]);
                }
            }
            Frames.Add(Frame);
        }

        private bool FirstFrame = false;
        public virtual void Update()
        {
            if (Play)
            {
                if (!Stop && CurrentFrame > NumberOfFrames)
                {
                    CurrentFrame = 0;
                }
                if (!FirstFrame)
                {
                    CurrentFrame = 0;
                    CurrentFrame += Speed;
                    FirstFrame = true;
                }
                else
                {
                    CurrentFrame += Speed;
                }
            }
            else if (PlayBackward)
            {
                if (!Stop && CurrentFrame >= 0)
                {
                    CurrentFrame = NumberOfFrames;
                }
                CurrentFrame -= Speed;
            }            
        }

        protected KeyFrame_I3DObject LastFramePlayed;
        public virtual void UpdateAnimation(bool Adding, int dif)
        {
            if (Object == null)
                return;

            KeyFrame_I3DObject Frame = FindFrame(CurrentFrame);
            
            int Difference = dif;
            if (Difference < 0)
                Difference *= -1;
            if (Difference == 0)
                Difference = 1;
            if (Frame != null)
            {
                if (ApplyPosition)
                    Object.Position = Frame.Position;
                Object.Scale = Frame.Scale;
                Object.RadiansRotation = Frame.Rotation;
            }
            else
            {
                if (InterpolatedAnimation)
                {
                    Frame = FindNextFrame();
                    if (Frame == null)
                        return;

                    LastFramePlayed = FindLastKeyFrame(Frame);
                    if (LastFramePlayed == null)
                        return;

                    if (Frame != null)
                    {
                        float FramesDifference = (Frame.Frame - LastFramePlayed.Frame);

                        //// POSITION
                        Vector3 Position = LastFramePlayed.Position;
                        Vector3 PosDirection = Frame.Position - Position;
                        Vector3 PosIncrement = PosDirection / FramesDifference;
                        //// ROTATION
                        Vector3 Rotation = LastFramePlayed.Rotation;
                        Vector3 RotDirection = Frame.Rotation - Rotation;
                        Vector3 RotIncrement = RotDirection / FramesDifference;
                        //// SCALE
                        Vector3 Scale = LastFramePlayed.Scale;
                        Vector3 ScaDirection = Frame.Scale - Scale;
                        Vector3 ScaIncrement = ScaDirection / FramesDifference;

                        if (Adding)
                        {
                            if (ApplyPosition)
                            {
                                if (PosIncrement == Vector3.Zero)
                                {
                                    Object.Position = Frame.Position;
                                }
                                else
                                {
                                    Object.Position += PosIncrement * Difference;
                                }
                            }
                            Object.RadiansRotation += RotIncrement * Difference;
                            Object.Scale += ScaIncrement * Difference;
                        }
                        else
                        {
                            if (ApplyPosition)
                            {
                                Object.Position -= PosIncrement * Difference;
                            }
                            Object.RadiansRotation -= RotIncrement * Difference;
                            Object.Scale -= ScaIncrement * Difference;
                        }
                    }
                }
            }
        }
        protected KeyFrame_I3DObject FindFrame(int Frame)
        {
            for (int i = 0; i < Frames.Count; i++)
                if (Frames[i].Frame == Frame)
                    return Frames[i];
            return null;
        }
        protected KeyFrame_I3DObject FindLastKeyFrame(KeyFrame_I3DObject frame)
        {
            KeyFrame_I3DObject lastFoundFrame = null;
            for (int i = 0; i < Frames.Count; i++)
            {
                if (lastFoundFrame == null)
                {
                    if (Frames[i].Frame < frame.Frame)
                        lastFoundFrame = Frames[i];
                }
                else
                {
                    if (Frames[i].Frame < frame.Frame &&
                        Frames[i].Frame > lastFoundFrame.Frame)
                        lastFoundFrame = Frames[i];
                }
            }

            return lastFoundFrame;
        }
        protected KeyFrame_I3DObject FindNextFrame()
        {
            int MinFrame = this.CurrentFrame;
            KeyFrame_I3DObject frameFound = null;

            for (int i = 0; i < Frames.Count; i++)
            {
                if (frameFound == null && Frames[i].Frame > MinFrame)
                {
                    frameFound = Frames[i];
                }
                if (frameFound != null)
                {
                    if (Frames[i].Frame > MinFrame &&
                        Frames[i].Frame < frameFound.Frame)
                        frameFound = Frames[i];
                }
            }

            return frameFound;
        }

        public virtual void SerializeData(SerializeData Data)
        {
            Data.AddData("Animation.Frames", FromKeyFrameListToString(Frames));
            Data.AddData("Animation.ApplyPosition", this.ApplyPosition);
            Data.AddData("Animation.Play", this.Play);
            Data.AddData("Animation.PlayBackward", this.PlayBackward);
            Data.AddData("Animation.Stop", this.Stop);
            Data.AddData("Animation.InterpolatedAnimation", this.InterpolatedAnimation);
            Data.AddData("Animation.Speed", this.Speed);
        }
        public virtual void DeserializeData(SerializeData Data)
        {
            this.Frames = FromStringToListKeyFrame(Data.GetData<string>("Animation.Frames"));

            this.ApplyPosition = Data.GetData<bool>("Animation.ApplyPosition");
            this.Play = Data.GetData<bool>("Animation.Play");
            this.PlayBackward = Data.GetData<bool>("Animation.PlayBackward");
            this.Stop = Data.GetData<bool>("Animation.Stop");
            this.InterpolatedAnimation = Data.GetData<bool>("Animation.InterpolatedAnimation");
            this.Speed = Data.GetData<int>("Animation.Speed");

            if (Frames.Count > 0)
            {
                KeyFrame_I3DObject frame = FindFrame(0);
                Object.Position = frame.Position;
                Object.RadiansRotation = frame.Rotation;
                Object.Scale = frame.Scale;
            }
        }

        public static string FromKeyFrameListToString(List<KeyFrame_I3DObject> Frames)
        {
            string s = String.Empty;

            for (int i = 0; i < Frames.Count; i++)
            {
                string keyframe = "(" + FromKeyFrameToString(Frames[i]) + ")";
                s += keyframe;
            }

            return s;
        }
        public static List<KeyFrame_I3DObject> FromStringToListKeyFrame(string str)
        {
            List<KeyFrame_I3DObject> list = new List<KeyFrame_I3DObject>();
            if (str != null)
            {              
                string[] div = str.Split("(".ToCharArray());
                for (int i = 0; i < div.Length; i++)
                {
                    div[i] = div[i].Replace(")", "");

                    string r = div[i];

                    if (!String.IsNullOrEmpty(r))
                    {
                        KeyFrame_I3DObject frame = FromStringToKeyFrame(r);
                        list.Add(frame);
                    }
                }
            }
            return list;
        }
        public static string FromKeyFrameToString(KeyFrame_I3DObject frame)
        {
            string s = frame.Frame.ToString() + "|" + Data.FromVector3ToString(frame.Position) + "|" +
                Data.FromVector3ToString(frame.Rotation) + "|" + Data.FromVector3ToString(frame.Scale);
            return s;
        }
        public static KeyFrame_I3DObject FromStringToKeyFrame(string str)
        {            
            KeyFrame_I3DObject frame = new KeyFrame_I3DObject();
            string[] div = str.Split("|".ToCharArray());
            int Keyframe = int.Parse(div[0]);
            Vector3 position = Data.FromStringToVector3(div[1]);
            Vector3 rotation = Data.FromStringToVector3(div[2]);
            Vector3 scale = Data.FromStringToVector3(div[3]);
            frame.Frame = Keyframe;
            frame.Position = position;
            frame.Rotation = rotation;
            frame.Scale = scale;
            return frame;
        }


        public ICloneable Clone()
        {
            Animation_I3DObject animation = new Animation_I3DObject();
            for (int i = 0; i < this.Frames.Count; i++)
            {
                animation.Frames.Add(this.Frames[i]);
            }

            animation.ApplyPosition = this.ApplyPosition;
            animation.InterpolatedAnimation = this.InterpolatedAnimation;
            animation.Play = this.Play;
            animation.PlayBackward = this.PlayBackward;
            animation.Stop = this.Stop;

            return animation;
        }
    }
}
